/**
 *
 * 114所报告
 *
 * Created by nongzhizhong on 2024/06/05
 */

const fs = require('fs-extra');
const path = require('path');
const compressing = require('compressing');
const BaseReporter = require('../base-report');
const tests = require('../../../tests');
const functionModels = require('../../../functions');
const classCreator = require('../base-report-test-case-transformer');
const { versionType, getRelationVersions, diffProjectVersions } = require('../../../project-versions');
const utility = require('../../../utility');
const logger = require('../../../logger');
const File = require('../../../mongo/file');
const ProjectVersion = require('../../../mongo/project-version');
const invokeRelations = require('../../../../models/invoke-relations');
const { getDefects } = require('../../../defects');

class Reporter804 extends BaseReporter {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null, format, complete) {
    super(versionId, locale, version, project, creator, outputDir, __dirname, format);
    this.complete = complete || false;
    this.functionIndex = 1;
    this.testedFunctionIndex = 1;
    this.unTestFunctionIndex = 1;
    this.data = {};
    this.headerFilesSuffixes = [];
    this.sourceFilesSuffixes = [];
    this.ReportTestCaseTransformer = classCreator(this.isIntegrationTest() && 'integrationTest');
  }

  async generate(progressIndicator) {
    const result = {
      cachePath: this.outputDir,
    };
    this.overridingGetAreas();
    this.handleVersionSuffixes();
    this.data = {
      projectName: this.project.projectName,
      versionName: this.version.versionName,
      userName: this.creator.username,
      isRegressionVersion: this.isRegressionVersion(),
      testcaseCount: 0,
      testedFuncCount: 0,
      unTestFuncCount: 0,
      coverages: [],
      files: [],
    };
    if (this.version.coverages) {
      const { coverages } = this.version;
      if (coverages & tests.coverageKind.statement) {
        this.data.coverages.push({
          name: '语句',
          kind: tests.coverageKind.statement,
        });
      }
      if (coverages & tests.coverageKind.branch) {
        this.data.coverages.push({
          name: '分支',
          kind: tests.coverageKind.branch,
        });
      }
      if (coverages & tests.coverageKind.condition) {
        this.data.coverages.push({
          name: '条件',
          kind: tests.coverageKind.condition,
        });
      }
      if (coverages & tests.coverageKind.decision) {
        this.data.coverages.push({
          name: '决策',
          kind: tests.coverageKind.decision,
        });
      }
      if (coverages & tests.coverageKind.mcdc) {
        this.data.coverages.push({
          name: 'MC/DC',
          kind: tests.coverageKind.mcdc,
        });
      }
    }
    if (this.isRegressionVersion()) {
      const oldVersion = await ProjectVersion.findById(this.version.oldVersionId, ['versionName']);
      const diffData = await diffProjectVersions(this.creator, this.versionId, this.version.oldVersionId);
      this.data.addFiles = diffData.addFiles.map((filePath) => ({ filePath }));
      this.data.modifyFiles = diffData.modifyFiles.map((filePath) => ({ filePath }));
      this.data.deleteFiles = diffData.deleteFiles.map((filePath) => ({ filePath }));
      this.data.deleteNumberOfLines = diffData.deletion;
      this.data.insertNumberOfLines = diffData.insertion;
      this.data.oldVersionName = oldVersion.versionName;
      this.data.addFuncCount = 0;
      this.data.modifyFuncCount = 0;
      this.data.sourceFileCount = 0;
      this.data.headerFileCount = 0;
      this.data.functionCount = 0;
      this.data.numberOfLines = 0;
      this.data.sourceNumberOfLines = 0;
      this.data.headerNumberOfLines = 0;
    }
    const { versionPath, projectDir } = await this.spliteByProjectVersion();
    this.versionDir = versionPath;
    this.projectDir = projectDir;
    const reportFileName = this.getReportName();
    const zipFileName = `${this.project.projectName}-${this.version.versionName}.${reportFileName}`;
    let templateName = 'file-template';
    if (this.isIntegrationTest()) {
      templateName = 'file-template-it';
    }
    this.data.files = await this.handleFiles(this.versionDir, progressIndicator);
    if (this.version.versionType === versionType.unitTest) {
      this.data.staticAnalyze = await this.getStaticAnalyze();
    }
    await this.generateReport(templateName, this.data, `${path.join(this.versionDir, `${this.project.projectName}${reportFileName}`)}`);
    result.reportPath = path.join(this.outputDir, `${zipFileName}.zip`);
    const reportsDir = path.join(this.outputDir, this.project.projectName);
    await compressing.zip.compressDir(reportsDir, result.reportPath);
    return result;
  }

  handleVersionSuffixes() {
    if (this.version.headers && this.version.headers.suffixes) {
      this.headerFilesSuffixes = this.version.headers.suffixes;
    }
    if (this.version.languages) {
      Object.keys(this.version.languages).forEach((lanKey) => {
        this.sourceFilesSuffixes.push(...(this.version.languages[lanKey].suffixes));
      });
    }
  }

  getReportName() {
    if (this.isIntegrationTest()) {
      return this.isRegressionVersion() ? this.i18n.__('tests.integrationTestRegressionReport') : this.i18n.__('tests.integrationTestReport');
    }
    return this.isRegressionVersion() ? this.i18n.__('tests.unitTestRegressionReport') : this.i18n.__('tests.unitTestReport');
  }

  isRegressionVersion() {
    return !!this.version.oldVersionId;
  }

  // 重写对象的getAreas方法，调整数据顺序
  overridingGetAreas() {
    this.ReportTestCaseTransformer.prototype.getAreas = function getAreas() {
      return [
        this.objectData,
        this.paramVariablesData,
        this.globalVariablesData,
        this.staticsData,
        this.stubsOutputData,
        this.mallocVariablesData,
        this.fixedAddressExpressionsData,
        this.fixedAddressBasesData,
        this.returnValueData,
        this.objectOutputData,
        this.globalOutputData,
        this.staticsOutputData,
        this.mallocOutputData,
        this.fixedAddressBasesOutputData];
    };
  }

  overridingGetAreasStubs(transformInstance) {
    transformInstance.getAreas = function getAreas() {
      if (this.stubsData && this.stubsData.length > 0) {
        this.stubsData.forEach((stub) => {
          if (stub.name) {
            stub.name += '打桩';
          }
          if (stub.typeName) {
            stub.typeName = `将${stub.typeName}`;
          }
        });
      }
      return [this.stubsData];
    };
  }

  static versionTypeSupport(vType) {
    return vType === versionType.unitTest || vType === versionType.integrationTest;
  }

  async handleFiles(versionDir, progressIndicator) {
    await fs.ensureDir(this.versionDir);
    const sourceFiles = await File.find({
      versionId: this.versionId,
      extname: { $in: [...this.headerFilesSuffixes, ...this.sourceFilesSuffixes] },
    }, ['_id', 'fileName', 'path', 'status', 'numberOfLines', 'extname']);
    let index = 1;
    return utility.arrayChunkOperation(sourceFiles, 1, async (file) => {
      if (this.isRegressionVersion()) {
        this.data.numberOfLines += file.numberOfLines;
        if (this.headerFilesSuffixes.includes(file.extname)) {
          this.data.headerFileCount += 1;
          this.data.headerNumberOfLines += file.numberOfLines;
        } else if (this.sourceFilesSuffixes.includes(file.extname)) {
          this.data.sourceFileCount += 1;
          this.data.sourceNumberOfLines += file.numberOfLines;
        }
      }
      const result = await this.handleFile(file, versionDir);
      result.fileIndex = index;
      index += 1;
      if (progressIndicator) {
        progressIndicator.advance(1 / sourceFiles.length);
      }
      return result;
    });
  }

  handleConveragePercent(coverage) {
    return `${Math.round(coverage * 10000) / 100}%`;
  }

  async handleFile(file) {
    const result = {
      fileName: file.fileName,
      filePath: file.path,
      numberOfLines: file.numberOfLines,
      unTestFuncs: [], // 未测试函数
      testedFuncs: [], // 已测试函数
    };
    const superFile = await super.handleFile(file, this.versionDir);
    if (this.isRegressionVersion()) {
      result.addFuncs = [];
      result.modifyFuncs = [];
      this.data.functionCount += superFile.functions.length;
    }
    (await this.getFunctionDetails(superFile.functions, superFile.typeSystem))
      .sort((a, b) => a.index - b.index)
      .forEach((func, index) => {
        if (this.isRegressionVersion() && func.changed) {
          const obj = { functionName: func.functionName };
          switch (func.changed) {
            case 'added': {
              this.data.addFuncCount += 1;
              result.addFuncs.push(obj);
              break;
            }
            case 'modified': {
              this.data.modifyFuncCount += 1;
              result.modifyFuncs.push(obj);
              break;
            }
            default: break;
          }
        }
        this.data.testcaseCount += func.funcTestcaseCount;
        const sign = this.isIntegrationTest() ? !!func.invokeFuncs : func.funcTestcaseCount > 0;
        if (sign) {
          func.funcIndex = this.testedFunctionIndex;
          func.index = index + 1;
          result.testedFuncs.push(func);
          this.testedFunctionIndex += 1;
          this.data.testedFuncCount += 1;
          return;
        }
        func.funcIndex = this.unTestFunctionIndex;
        result.unTestFuncs.push(func);
        this.unTestFunctionIndex += 1;
        this.data.unTestFuncCount += 1;
      });
    return result;
  }

  /**
  *  获取函数名
  * @param {String} functionName 函数名
  * @param {Object} params 函数variable形参
  */
  getFunctonName(functionName) {
    const regExpr = /(.*)\(.*\)/;
    if (functionName.match(regExpr)) {
      [, functionName] = functionName.match(regExpr);
    }
    return functionName;
  }

  /**
   *  获取函数详情
   * @param {Array[Object]} functions 函数
   * @param {Object} typeSystem 文件类型系统
   */
  async getFunctionDetails(functions, typeSystem) {
    const resFunctions = [];
    const { coverages } = this.version;
    await utility.arrayChunkOperation(functions, 10, async (func) => {
      const newFunc = {
        functionName: this.getFunctonName(func.functionName),
        description: func.description || '',
        funcTestcaseCount: func.tests ? func.tests.length : 0,
        invokeDepthMax: 1,
        invokePathCount: 1,
        testcaseIndexRange: '',
        testcases: [],
        funcCoverages: [],
        invokeFuncs: null,
        changed: func.changed,
      };
      if (func.tests.length > 0) {
        const funcVariables = functionModels.getFunctionVariableJson(func.variables);
        const testcases = await this.getTestDetailsOfFunction(typeSystem, func, funcVariables, func.tests);
        newFunc.testcaseIndexRange = testcases.length > 1
          ? `${testcases[0].testcaseIndex}~${testcases[testcases.length - 1].testcaseIndex}` : testcases[0].testcaseIndex;
        newFunc.testcases = testcases;
      }
      if (coverages & tests.coverageKind.statement) {
        newFunc.funcCoverages.push(
          {
            name: '语句覆盖',
            value: (!func.statementCovered && !func.statementTotal) ? '/' : this.handleConveragePercent(func.statementCovered / func.statementTotal),
          }
        );
      }
      if (coverages & tests.coverageKind.branch) {
        newFunc.funcCoverages.push(
          {
            name: '分支覆盖',
            value: (!func.branchCovered && !func.branchTotal) ? '/' : this.handleConveragePercent(func.branchCovered / func.branchTotal),
          }
        );
      }
      if (coverages & tests.coverageKind.condition) {
        newFunc.funcCoverages.push({
          name: '条件覆盖',
          value: (!func.conditionCovered && !func.conditionTotal) ? '/' : this.handleConveragePercent(func.conditionCovered / func.conditionTotal),
        });
      }
      if (coverages & tests.coverageKind.decision) {
        newFunc.funcCoverages.push({
          name: '决策覆盖',
          value: (!func.decisionCovered && !func.decisionTotal) ? '/' : this.handleConveragePercent(func.decisionCovered / func.decisionTotal),
        });
      }
      if (coverages & tests.coverageKind.mcdc) {
        newFunc.funcCoverages.push(
          {
            name: 'MC/DC覆盖',
            value: (!func.mcdcCovered && !func.mcdcTotal) ? '/' : this.handleConveragePercent(func.mcdcCovered / func.mcdcTotal),
          }
        );
      }
      if (this.isIntegrationTest()) {
        ['invoke', 'invokedFunc'].forEach((key) => {
          newFunc[`${key}Coverage`] = '-';
          if (func[`${key}Total`]) {
            newFunc[`${key}Coverage`] = `${Math.round((func[`${key}Covered`] / func[`${key}Total`]) * 10000) / 100}%`;
          }
        });
        const relationFuncs = await invokeRelations.retrieveRelations(func._id,
          {
            sort: true,
            initRelations: true,
            completeDepth: true,
          });
        relationFuncs.forEach((invokeFunc) => {
          if (invokeFunc.depth && invokeFunc.depth.depths) {
            newFunc.invokeDepthMax = Math.max(newFunc.invokeDepthMax, invokeFunc.depth.maxDepth || invokeFunc.depth.minDepth);
          }
          if (invokeFunc.fromFuncId && invokeFunc.fromFuncId.toString() === func._id.toString()) {
            invokeFunc.invokePathCount += 1;
            const toFuncName = this.getFunctonName(invokeFunc.toFuncName);
            newFunc.invokeFuncs = newFunc.invokeFuncs ? `${newFunc.invokeFuncs},${toFuncName}` : toFuncName;
          }
        });
      }
      resFunctions.push(newFunc);
    });
    return resFunctions;
  }

  /**
   * 获取测试用例详情
   * @param {Object} typeSystem 文件类型系统
   * @param {Object} func  函数
   * @param {Object} funcVariables 函数variable
   * @param {Array[Object]} testcases 测试用例
   * @param {Object} cfg 函数cfg
   */
  async getTestDetailsOfFunction(typeSystem, func, funcVariables, testcases) {
    const testcasesDetails = [];
    testcases = testcases.sort((a, b) => (a.identifierNumber > b.identifierNumber ? 1 : -1));
    testcases.forEach((testcase, index) => {
      const newTestcase = tests.getJson(testcase, this.i18n);
      const transformer = new this.ReportTestCaseTransformer(
        newTestcase,
        funcVariables,
        typeSystem,
        this.i18n
      );
      const transformerStubs = new this.ReportTestCaseTransformer(
        newTestcase,
        funcVariables,
        typeSystem,
        this.i18n
      );
      this.overridingGetAreasStubs(transformerStubs);
      try {
        transformer.transform(this.complete);
        transformerStubs.transform(this.complete);
      } catch (error) {
        logger.error(`testCase transform error: fileId: ${func.fileId} function name: ${func.functionName} test case number: ${testcase.number}:`, error);
      }
      const newInputDetails = [];
      const newOutputDetails = [];
      const inputStubs = [];
      const regExpr = /第[\d]+次调用/g;
      transformerStubs.input.forEach((input) => {
        // eslint-disable-next-line no-control-regex
        input.variable = input.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
        if (input.variable.match(regExpr)) {
          input.variable += '返回值为';
        }
        // 处理变量值显示格式
        if (input.value) {
          input.value = ` = ${input.value}`;
        }
        if (input.category && input.variable) {
          inputStubs.push({
            category: `${input.category}:`,
            value: '',
            variable: '',
          });
          inputStubs.push({
            category: '',
            value: input.value,
            variable: input.variable,
          });
        } else {
          inputStubs.push(input);
        }
      });
      transformer.input.forEach((input) => {
        // eslint-disable-next-line no-control-regex
        input.variable = input.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
        // 处理变量值显示格式
        if (input.value) {
          input.value = ` = ${input.value}`;
        }
        if (input.category && input.variable) {
          newInputDetails.push({
            category: `${input.category}:`,
            value: '',
            variable: '',
          });
          newInputDetails.push({
            category: '',
            value: input.value,
            variable: input.variable,
          });
        } else {
          newInputDetails.push(input);
        }
      });
      transformer.output.forEach((output) => {
        // eslint-disable-next-line no-control-regex
        output.variable = output.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
        // 处理变量值显示格式
        if (output.result) {
          output.result = ` = ${output.result}`;
        }
        if (output.value) {
          output.value = ` = ${output.value}`;
        }
        if (output.category && output.variable) {
          newOutputDetails.push({
            category: `${output.category}:`,
            value: '',
            result: '',
            variable: '',
          });
          newOutputDetails.push({
            category: '',
            value: output.value,
            result: output.result,
            variable: output.variable,
          });
        } else {
          newOutputDetails.push(output);
        }
      });
      testcasesDetails.push({
        testId: testcase._id,
        hasBj: testcase.kind && !!(tests.testKind.borders & testcase.kind),
        hasBranch: testcase.kind && !!(tests.testKind.branch & testcase.kind),
        testcaseIndex: `${'0'.repeat(3 - `${index + 1}`.length)}${index + 1}`,
        input: newInputDetails,
        inputStubs,
        initCode: transformer.prelude,
        output: newOutputDetails,
      });
    });
    return testcasesDetails;
  }

  /**
   * 包含所有测试用例
   * @returns
   */
  filterTestCasesByResultKind() {
    return false;
  }

  async getStaticAnalyze() {
    const ret = {
      allFuncs: [],
      allRules: [],
    };
    const [staticVersion] = (await getRelationVersions(this.versionId)).filter((ver) => ver.versionType === versionType.staticAnalyze);
    if (!staticVersion) {
      return ret;
    }
    const allFiles = await this.getDataAdapter().getFiles(staticVersion._id);
    let allFuncs = await this.getDataAdapter().getFunctions(staticVersion._id);

    const fileMap = new Map(allFiles.map((f) => [f._id.toString(), f]));
    const allDefects = await getDefects(staticVersion._id);
    const rulesMap = allDefects.reduce((obj, def) => {
      const ruleId = def.ruleId.toString();
      obj[ruleId] = obj[ruleId] || {
        ruleSetName: def.ruleSetName,
        ruleCode: def.ruleCode,
        ruleName: def.ruleName,
        levelName: def.levelName,
        defects: [],
      };
      obj[ruleId].defects.push(def);
      return obj;
    }, {});
    allFuncs = allFuncs.map((func) => {
      func = func.toObject();
      const file = fileMap.get(func.fileId.toString());
      func.fileName = file ? file.fileName : '-';
      func.commentRate = parseInt((func.commentLines / func.numberOfLines) * 10000, 10) / 100;
      return func;
    });
    return {
      allFuncs,
      issueFuncs: allFuncs.filter((fun) => fun.fanIn === 0
        || fun.fanOut > 7
        || fun.cycleComplexity > 10
        || fun.sourceLines > 200
        || fun.commentRate < 20),
      allRules: Object.values(rulesMap).map((item, index) => ({
        ...item,
        index: index + 1,
      })),
    };
  }
}

module.exports = Reporter804;
